package com.googlecode.contests_repo.zip;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;

import com.googlecode.contests_repo.core.model.ResourcesUtils;
import com.googlecode.contests_repo.core.users.AccessRight;
import com.googlecode.contests_repo.core.users.UserManager;

/**
 * Abstract class for zipping/unzipping resources.
 * 
 * Instantiate a subclass to work with a specific type of resource (problem, contest, series)
 * and use its {@link #fetchZipped(String, String)} method to zip the resource data.
 * 
 * Please note, that the information that will be included in the zip file depends
 * on the access rights of the user who will later download the file. 
 * 
 * @author deni, boyan
 */
public abstract class ResourceZiper {

	/**
	 * Fetches a given resource in zip format.
	 * 
	 * @param username
	 * 		the user who will later download the file
	 * @param resourcePath
	 * 		the path to the resource that should be zipped
	 * @return
	 * 		a zip file containing the filtered resource data 
	 * @throws IOException
	 */
	public File fetchZipped(String username, String resourcePath) throws IOException {
		File tempFile = File.createTempFile("resource", ".zip");
		ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(tempFile));
		
		String lastComponent = new File(resourcePath).getName();
		
		try {
			addResourceToZip(username, resourcePath, lastComponent, zip);
			
			zip.flush();
			zip.close();
		} catch (ZipException e) {
			return null;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return tempFile;
	}
	
	/**
	 * Returns a file path, extended with a last component
	 * @param path
	 * 		The path to extend
	 * @param toAppend
	 * 		The component to append
	 * @return
	 * 		The resultant extended path
	 */
	protected String getExtendedFilePath(String path, String toAppend) {
		return "".equals(path) ? toAppend : path + File.separator + toAppend;
		
	}
	
	private void addResourceToZip(String username, String resourcePath, String zipPath, ZipOutputStream zip) throws Exception {		
		AccessRight right = UserManager.getAccessRight(username, resourcePath);
		
		if (!right.includes(AccessRight.VIEW)) {
			return;
		}
		
		processJsonFile(right, resourcePath, zipPath, zip);
		processResourceSpecificFiles(right, resourcePath, zipPath, zip);
				
		if (shouldIncludePublicFiles(resourcePath, right)) {
			String publicExtrasFolderPath = resourcePath + File.separator + "_public_files";
			File publicExtrasFolder = new File(publicExtrasFolderPath);
			if (publicExtrasFolder.exists()) {
				addFileToZip(publicExtrasFolderPath, zipPath, zip);
			}
		}
		
		if (right.includes(AccessRight.VIEW_FULL)) {
			String privateExtrasFolderPath = resourcePath + File.separator + "_files";
			File privateExtrasFolder = new File(privateExtrasFolderPath);
			if (privateExtrasFolder.exists()) {
				System.out.println(privateExtrasFolderPath);
				addFileToZip(privateExtrasFolderPath, zipPath, zip);
			}
		}
		
		if (shouldIncludeChildResources(resourcePath, right)) {
			ResourceZiper childZiper = getChildZiper();
			List<File> childFolders = ResourcesUtils.getResourceSubfolders(new File(resourcePath));
			for (File folder : childFolders) {
				String path = getExtendedFilePath(zipPath, folder.getName());
				childZiper.addResourceToZip(username, folder.getPath(), path, zip);
			}
		}
	}

	/**
	 * Checks whether the sub-resources should be included in the zip file.
	 * 
	 * @param resourcePath
	 * 		Path to the resource whose sub-resources should be checked.
	 * @param right
	 * 		The access right of the user who will download the zip file.
	 * @return
	 * 		True if the sub-resources should be included, false otherwise.
	 */
	protected boolean shouldIncludeChildResources(String resourcePath, AccessRight right) {
		return (right.includes(AccessRight.VIEW));
	}

	/**
	 * Checks whether the _public_files folder should be included in the zip file.
	 * 
	 * @param resourcePath
	 * 		Path to the resource whose _public_files folder should be checked.
	 * @param right
	 * 		The access right of the user who will download the zip file.
	 * @return
	 * 		True if the _public_files folder should be included, false otherwise.
	 */
	protected boolean shouldIncludePublicFiles(String resourcePath, AccessRight right) {
		return right.includes(AccessRight.VIEW);
	}

	/**
	 * Adds the json file of the resource to the zip (problem.json, contest.json or series.json).
	 * Takes into account the access rights of the user who will download the resources -
	 * for example, title and about properties will always be added,
	 * but notes will be added only if the user has VIEW_FULL right.
	 * 
	 * @param right
	 * 		The access right of the user who will download the zip.
	 * @param resourcePath
	 * 		The path to the resource to add.
	 * @param zipPath
	 * 		The current path in the zip file. 
	 * @param zip
	 * 		Output stream used for adding the resource.
	 * @throws IOException 
	 * @throws JsonMappingException 
	 * @throws JsonParseException 
	 */
	public abstract void processJsonFile(AccessRight right, String resourcePath,
			String zipPath, ZipOutputStream zip) throws JsonParseException, JsonMappingException, IOException;
	
	/**
	 * Adds resource-specific files to the zip. For example, problem description,
	 * solution and tests.
	 * 
	 * By default does nothing.
	 * 
	 * @param right
	 * 		The access right on the problem of the users that will download the zip.
	 * @param resourcePath
	 * 		The path to the resource that should be zipped.
	 * @param zipPath
	 * 		The path in the zip file (the structure in the zip file should mirror
	 * 		the resource hierarchy).
	 * @param zip
	 * 		Output stream used for adding the files.
	 * @throws Exception
	 */
	public void processResourceSpecificFiles(AccessRight right, String resourcePath,
			String zipPath, ZipOutputStream zip) throws Exception {
		// by default do nothing
	}
	
	/**
	 * Gets a {@link ResourceZiper} capable of working with this resource's children.
	 * For example, if the current zipper works with contests, this method should return
	 * a zipper for problems.
	 * 
	 * @return
	 * 		a {@link ResourceZiper} capable of working with child resources.
	 */
	public abstract ResourceZiper getChildZiper();
	
	/**
	 * Adds a file or folder to the zip file.
	 * 
	 * @param srcFile
	 * 		The file or folder that should be added to the file.
	 * @param zipPath
	 * 		The path in the zip file.
	 * @param zip
	 * 		An output stream to use for adding the file or folder.
	 * @throws Exception
	 */
	public void addFileToZip(String srcFile, String zipPath, ZipOutputStream zip)
	throws Exception {
		File folder = new File(srcFile);
		if (folder.isDirectory()) {
			addFolderToZip(srcFile, zipPath, zip);
		} else {
			byte[] buf = new byte[1024];
			int len;
			FileInputStream in = new FileInputStream(srcFile);
			
			String path = getExtendedFilePath(zipPath, folder.getName());
			zip.putNextEntry(new ZipEntry(path));
			while ((len = in.read(buf)) > 0) {
				zip.write(buf, 0, len);
			}
		}
	}

	private void addFolderToZip(String srcFolder, String zipPath, ZipOutputStream zip)
	throws Exception {
		File folder = new File(srcFolder);

		for (String fileName : folder.list()) {
			addFileToZip(srcFolder + File.separator + fileName, getExtendedFilePath(zipPath, folder.getName()), zip);
		}
	}
}
